iT邦幫忙

2022 iThome 鐵人賽

DAY 27
0
Modern Web

開始搞懂React生態系系列 第 27

Day 27 React 的 CSS 解決方案

  • 分享至 

  • xImage
  •  

讓 CSS 只跟著元件

我們知道若元件拆分得越細,可以在其他元件內的重複組合使用的頻率就越高,所以會希望 CSS 只跟隨著元件,當這個元件被拿去其他元件使用時,也不會因為要考慮樣式的問題,而造成不易重複使用。

那在 CSS 的結構設計上,我們要怎麼處理呢?主要可以分成以下幾種方式。

  • 方案一:namespaces
  • 方案二:CSS in JS
  • 方案三:CSS Modules
  • 方案四:utility-first CSS Framework

接下來就來簡單說明這四種處理方式。

方案一:namespaces

假設我們有二個元件 - ComponentA、ComponentB

我們可以在引入的樣式檔案加上 namespaces 標示為不同元件的樣式

.ComponentA.title { color: red; }
.ComponentA .xxx {...}
.ComponentB.title { font-size: 40px; }
.ComponentB .xxx {...}
<div class="ComponentA">
  <h1 class="title">元件A</h1>
</div>
<div class="ComponentB">
  <h1 class="title">元件B</h1>
</div>

不過若 ComponentB 是 ComponentA 的子元件時,可能會發生 ComponentA 的樣式作用在了 ComponentB 上。

<div class="ComponentA">
  <h1 class="title">元件A</h1>
  <!-- ComponentB 是 ComponentA 的子元件 --> 
    <div class="ComponentB">
    <h1 class="title">元件B</h1>
  </div>    
</div>

改善的方法可以遵循 BEM (Block, Element, Modifier) 的規範來設計。

.ComponentA__title { color: red; }
.ComponentB__title { font-size: 40px; }
<div class="ComponentA">
  <h1 class="ComponentA__title">元件A</h1>
  <!-- ComponentB 是 ComponentA 的子元件 --> 
    <div class="ComponentB">
    <h1 class="ComponentB__title">元件B</h1>
  </div>    
</div>

但是在多人維護的專案中使用 BEM 規範約定的方式來解決 CSS,開發者必須全盤深入理解專案的樣式設計結構,才有能力去組裝元件不影響樣式。

方案二:CSS in JS

透過把 CSS 寫在 JS 中,可以確保特定樣式只會作用在該元件,而且也可把 JS 中的一些邏輯判斷放到 CSS 使用。

React 有多第三方可用的 CSS-in-JS 套件,其中 styled-components 及 emotion 都很廣泛被使用。因為篇幅的關係這裡只介紹 styled-components

官網:https://styled-components.com/

安裝

npm install styled-components

語法

import styled from 'styled-components';
const styled元件 = styled.你要用的DOM元素`css程式碼`
...
// 在 jsx 使用時
<styled元件>內容1</styled元件>
<styled元件>內容2</styled元件>

styled元件會在「指定的DOM元素」上,產生一個 hash 編碼的 class

<h1 class="sc-ftTHYK gmEgew">...</h1>
<h1 class="sc-ftTHYK gmEgew">...</h1>

相同的styled元件會產生同樣,且不與其他元件重複的class

範例-1:基本使用

// components/ComponentA.js
import styled from "styled-components";

const Title = styled.h1`
  font-size: 40px;
  color: red;
`;
const ComponentA = (props) => {
  return (
    <>
      <Title>{props.text}</Title>
      {props.children}
    </>
  );
};

export default ComponentA;
// components/ComponentB.js
import styled from "styled-components";

const Title = styled.h1`
  color: blue;
`;
const ComponentB = (props) => {
  return <Title>{props.text}</Title>;
};

export default ComponentB;
// App.js
import ComponentA from "./components/ComponentA";
import ComponentB from "./components/ComponentB";

export default function App() {
  return (
    <div>
      <ComponentA text="ComponentA">
        <ComponentB text="ComponentB-1" />
        <ComponentB text="ComponentB-2" />
      </ComponentA>
    </div>
  );
}

執行結果:https://codesandbox.io/s/styled-components-04ccsy

範例-2:傳遞參數給 styled-compontents

  • 可以使用傳入的 props 搭配 ES6 的字串模版,讓 css 根據 props 的值而變動。
// compontents/Title.js
import styled from "styled-components";
const BaseTitle = styled.h1`
  color: ${(props) => (props.color ? props.color : "black")};
`;
const Title = styled(BaseTitle)`
  font-size: 30px;
`;
export default Title;
// components/ComponentA.js
import Title from "./Title";
const ComponentA = (props) => {
  return (
    <>
      <Title color="green">{props.text}</Title>
      {props.children}
    </>
  );
};
export default ComponentA;
// components/ComponentB.js
import Title from "./Title";
const ComponentB = (props) => {
  return <Title color="purple">{props.text}</Title>;
};
export default ComponentB;

執行結果:https://codesandbox.io/s/styled-components-props-m8wlnk

雖然使用相同的 Title 元件,但因為它會在不同元件產生不同的 hash class,因此可以避免在不同地方使用到相同 css selector 而互相影響

方案三:CSS Modules

官網:https://github.com/css-modules/css-modules

使用 npx create-react-app 建立專案的同時,默認就支持 css-module

使用方式如下:

  • CSS 檔以 [name].module.css 的方式命名
  • 變數 形式導入 CSS 文件
import styles from './[name].module.css'
  • className 使用變數.在css檔定義好的class
className={styles.title}

範例-1:基本使用

因為在 CodeSandBox 無法實現,所以大家可以使用 create-react-app 自行建立專案來實作。

// App.js
import ComponentA from "./components/ComponentA";
import ComponentB from "./components/ComponentB";
const App = () => {
  return (
    <div>
      <ComponentA text="A">
        <ComponentB text="B"></ComponentB>
      </ComponentA>
    </div>
  );
}
export default App;
// components/ComponentA.js
import styles from "./ComponentA.module.css";
const ComponentA = (props) => {
  return (
    <>
      <h1 className={styles.textColor}>
        <span>Component</span>
        <span>{props.text}</span>
      </h1>
      {props.children}
    </>
  );
};
export default ComponentA;
// components/ComponentA.module.css
.textColor {
  color: red;
}
// components/ComponentB.js
import styles from "./ComponentB.module.css";
const ComponentB = (props) => {
  return (
    <>
      <h1 className={styles.textColor}>
        <span>Component</span>
        <span>{props.text}</span>
      </h1>
    </>
  );
};
export default ComponentB;
// components/ComponentB.module.css
.textColor {
  color: blue;
}

結果

css-module 會在 class 後面添加 hash 值,所以就算我們二個樣式檔案都使用相同名稱(eg. textColor)也不會互相衝突,因為經過 css-module 編譯過後會將 class 轉成一個唯一沒有重複的 class

範例-2: 加上 global,用來宣告全域的 class

css-module 允許使用 :global(.className) 的語法,這樣宣告出來的 class 就是 global,而不會被編譯成 hash class。

  • 調整 ComponentA 的樣式,加上 global
:global(.bgYellow) {
  background-color: yellow;
}
  • 調整 ComponentA、ComponentB HTML 結構,使用 global class
- <span>Component</span>
+ <span className="bgYellow">Component</span>

結果

方案四:utility-first CSS Framework

基本概念

utility-first CSS 把大致上 CSS 會用到的屬性都用單個 class 來表示。

.m-1 {
  margin: 0.25rem;
}
.w-16 {
  width: 16px;
}
.text-black {
  color: rgb(0 0 0);
}

直接把 utility class 加在你想要作用的 element 上,大部分的情況下可以不需要自己編寫樣式,於是你就可以非常迅速地把網站的各種部分建構出來,也可以很輕鬆地調整。

目前流行的 utility-first CSS Framework

  • Tailwindcss:https://tailwindcss.com/
  • Windi CSS:https://windicss.org/
  • Tachyons:https://tachyons.io/

可以做到樣式跟著元件

用這樣的寫法,就很像在元件上使用 inline-style,那麼元件無論怎麼搬動,都不用擔心樣式的問題。

比 inline-style 更有彈性

而且使用 class 而不是 inline-style,好處就是當你有特定的 class,要有客製化的需求,那麼你只要在設定檔調整好該 class 的定義即可,而不同去每個元件找到相同的 style 去做調整。

直觀又好懂的 RWD 斷點寫法

這邊使用 Tailwind 的語法來示意

所以若想要在不同裝置的尺寸改變元素的寬度,只要簡單的設置如下

<!-- Width of 16 by default, 32 on medium screens, and 48 on large screens -->
<img class="w-16 md:w-32 lg:w-48" src="...">

在 React 使用

這邊使用 Tailwind 來示意

安裝

  • 在 npm 下載 tailwind 相關套件
npm install -D tailwindcss postcss autoprefixer
  • 產生初始化 tailwind.config.js 設定檔
npx tailwindcss init -p

調整 tailwind.config.js 符合專案設置

module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

在 CSS 檔裡面用 Tailwind classes

如果專案有需要調整樣式的需求,可以在這邊做調整

@tailwind base;
@tailwind components;
@tailwind utilities;

// 如果專案的需求是改變 h1 的預設大小,使用 @layer base,再做更改

@layer base {
  h1 {
    @apply text-6xl text-red-500 my-5;
  }
}

在元件中使用

export default function App() {
  return (
    <div className="text-3xl font-bold underline">
      Hello world!
    </div>
  )
}

小結

當專案越來越複雜時,個人非常推薦使用 utility-first CSS Framework 來做為 React 元件的開發。它能讓樣式跟著元件,也維持著 class 是可以很容易做調整的特性。在現代幾乎都要做 RWD 的網站專案下,因為它提供了直觀又好懂的 RWD 斷點寫法,也容易做出重用性很高且同時符合 RWD 的元件。

若想要深入理解 utility-first CSS Framework,尤其是 TailwindCSS 的話,千萬不要錯過 TailwindCSS - 從零開始,這個鐵人賽系列文。

Next

在此之前,會發現到目前都還只有做單一頁面的元件組合。但是在真實世界當中,專案是由很多頁面組合而成。通常可以按下導覧列的連結,轉導至指定的頁面。這就是接下來要介紹 React Router 的功能,

Reference

https://iter01.com/16779.html

https://www.infoq.cn/article/ftlppdefo27prgqhlo5a

https://ithelp.ithome.com.tw/articles/10223071

https://ithelp.ithome.com.tw/articles/10240466

https://molly1024.medium.com/css-modules-%E6%98%AF%E4%BB%80%E9%BA%BC-%E7%82%BA%E4%BB%80%E9%BA%BC%E6%88%91%E8%A6%81%E6%94%B9%E7%94%A8-css-modules-what-is-css-modules-why-should-you-use-it-aeb7d2955c58

https://www.robinwieruch.de/react-css-styling/?fbclid=IwAR366o7vsx3QhW_A-oy_ZO7_vfOsvSA8Ws6b_PqU_ILulBVDGYUCW7dvXZ0

https://5xruby.tw/posts/tailwind-css-plugin

https://tailwindcss.com/docs/guides/create-react-app


上一篇
Day 26 zustand - 基於 Flux 與 Hook實現狀態管理的套件
下一篇
Day 28 React Router v6 (上)
系列文
開始搞懂React生態系30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Tim Hsu
iT邦新手 1 級 ‧ 2022-10-12 09:09:05

感謝推薦與置入!XD

哈哈哈 好文大家看!

我要留言

立即登入留言